/*
This source file is part of the open source TripleGasm project
Copyright (C) 2010 Roma Kovalev

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

For the latest info, see http://code.google.com/p/triplegasm
You can contact the author(s) via e-mail: endiannesz@gmail.com
*/

#include <cstdlib>
#include "ball.hpp"

using namespace std;
using namespace Ogre;
using namespace TripleGasm;
#define USE_PLAIN_COLOURS

Ball::Ball(SceneManager* pSm) : mIsAttached(false)
{
  mpEnt= pSm->createEntity("Sphere"); 

  // Currently testing cg bump mapping, define USE_PLAIN_COLOURS to just use diffuse
#ifndef USE_PLAIN_COLOURS
  switch (rand() % 3)
  {
  case 0:
    {
      mpEnt->setMaterialName("bump1");
      SubEntity* pSub = mpEnt->getSubEntity(0);
      pSub->setCustomParameter(1, Vector4(1, 0, 0, 1));
      mType= RED;
    }
    break;
  case 1:
    {
      mpEnt->setMaterialName("bump2");
      SubEntity* pSub = mpEnt->getSubEntity(0);
      pSub->setCustomParameter(1, Vector4(0, 1, 0, 1));
      mType= GREEN;
    }
    break;
  case 2:
    {
      mpEnt->setMaterialName("bump3");
      SubEntity* pSub = mpEnt->getSubEntity(0);
      pSub->setCustomParameter(1, Vector4(0, 0, 1, 1));
      mType= BLUE;
    }
    break;
  }
#else
  switch (rand() % 3)
  {
  case 0:
    {
      mpEnt->setMaterialName("Red");
      mType= RED;
    }
    break;
  case 1:
    {
      mpEnt->setMaterialName("Green");
      mType= GREEN;
    }
    break;
  case 2:
    {
      mpEnt->setMaterialName("Blue");
      mType= BLUE;
    }
    break;
  }
#endif

  // Every ball has a force that propels it in a random direction
  mForce.x= Real((rand() % 100) - 50);
  mForce.y= Real((rand() % 100) - 50);

  mpNode= pSm->getRootSceneNode()->createChildSceneNode(Vector3(
    static_cast<Real>((rand() % 2) ? 100 : -100), // 100 or -100 X position
    static_cast<Real>((rand() % 200) - 100),  // -100...100 Y position
    0));
  mpNode->attachObject(mpEnt);

#ifdef _DEBUG
  mpEnt->setQueryFlags(SPHERE);
#endif
}

// For vector storing reasons only, don't use this!
Ball::Ball() : mIsAttached(false)
{

}

Ball::~Ball()
{

}

void Ball::Move(const FrameEvent& evt)
{
  if (!mIsAttached) // If the sphere is not attached to the manipulator
  {
    // If the force is negative, it should be set to 0, and stop decreasing.
    mForce.x > 0 ? mForce.x -= evt.timeSinceLastFrame : mForce.x= 0;
    mForce.y > 0 ? mForce.y -= evt.timeSinceLastFrame : mForce.y= 0;

    // The gravity vector will always be the opposite of the current position
    // This way the sphere is always pulled to the center of the screen
    Vector3 mGravity= -mpNode->getPosition();
    // The gravity vector must be normalized,
    // this way the length of the vector will always stay the same
    mGravity.normalise();
    mGravity *= 50;
    // The direction will be a combination of two forces,
    // this allows random movement of the spheres with a semi-realistic
    // gravity effect.
    Vector3 direction(mGravity + mForce);

    // Move the sphere
    mpNode->translate(Vector3(direction.x, direction.y, 0) * evt.timeSinceLastEvent, Node::TS_WORLD);
  }
}

void Ball::AttachTo(BaseSphere* pSphere)
{
  BYTE numPoint= pSphere->ClosestAttachPoint(this);

  InheritPivot(pSphere->mpAttachPoints[numPoint]);

  // Best describe this with an example.
  // If this sphere attaches with "rS" on the "numPoint" side, then
  // at the coordinates "mpAttachPoints[numPoint]" is the sphere "rS",
  // and on the other side is this sphere (this pointer)
  BindAttachPoints( Mirror(direction(numPoint)), pSphere );

  mGraphPos= MirrorPos(pSphere, direction(numPoint));

  mIsAttached= true; // Prevents the sphere from moving.
}

void Ball::CorrectSphereAttachPoints(local_matrix pLocalMatrix)
{
  for (BYTE i= 0; i < 4; ++i)
  {
    // If this attach point does not have an owner
    if (mpAttachPoints[i] == nullptr)
    {
      pair<BYTE, BYTE> pos= MirrorPos(this, direction(i));
      // And in that attach points coordinates is another sphere
      if (pLocalMatrix[pos.first][pos.second] != nullptr)
      {
        // Link the two spheres
        auto pSphere= pLocalMatrix[pos.first][pos.second];
        BindAttachPoints(direction(i), pSphere);
      }
    }
  }

  // If there are no spheres where the attach points should be,
  // create some new attach points
  if (mpAttachPoints[LEFT] == nullptr)
    SetAttachPoint(LEFT, mpNode->createChildSceneNode(LEFT_POINT));

  if (mpAttachPoints[RIGHT] == nullptr)
    SetAttachPoint(RIGHT, mpNode->createChildSceneNode(RIGHT_POINT));

  if (mpAttachPoints[BOTTOM] == nullptr)
    SetAttachPoint(BOTTOM, mpNode->createChildSceneNode(BOTTOM_POINT));

  if (mpAttachPoints[TOP] == nullptr)
    SetAttachPoint(TOP, mpNode->createChildSceneNode(TOP_POINT));
}

void Ball::InheritPivot(Ogre::SceneNode* pNode)
{
  mpNode->detachAllObjects();
  mpNode->getParent()->removeChild(mpNode);
  GetSceneManager()->destroySceneNode(mpNode);
  mpNode= pNode;
  mpNode->attachObject(mpEnt);
  SetNodeData(this);
}

std::list<Ball*> Ball::IsIsolatedSphere()
{
  std::list<Ball*> spheres; // All the spheres that are linked
  spheres.push_back(this);
  isChecked= true;

  for (BYTE i= 0; i < 4; ++i)
  {
    auto p= GetAttachPointData(direction(i));
    if (p != nullptr) // Is there anything at the attach point?
      if (p->GetType() == GRAY) // Is it the main manipulator?
      {
        for_each(spheres.begin(), spheres.end(), [](Ball* pBall){pBall->isChecked= false;});
        return std::list<Ball*>(); // Empty list indicates that the sphere is not isolated;
      }
      else  if (!p->isChecked) // Have we already checked this sphere?
      {
        static_cast<Ball*>(p)->IsIsolatedSphere(&spheres);
        if (!spheres.size())
          return spheres;
      }
  }

  // The spheres are isolated, and will be deleted
  return spheres;
}

void Ball::IsIsolatedSphere(std::list<Ball*>* pSpheres)
{
  pSpheres->push_back(this);
  isChecked= true;

  for (BYTE i= 0; i < 4; ++i)
  {
    auto p= GetAttachPointData(direction(i));
    if (p != nullptr) // Is there anything at the attach point?
      if (p->GetType() == GRAY) // Is it the main manipulator?
      {
        for_each(pSpheres->begin(), pSpheres->end(), [](Ball* pBall){pBall->isChecked= false;});
        (*pSpheres)= std::list<Ball*>(); // Empty list indicates that the sphere is not isolated;
      }
      else  if (!p->isChecked) // Have we already checked this sphere?
      {
        static_cast<Ball*>(p)->IsIsolatedSphere(pSpheres);
      }
  }
}

std::list<Ball*> Ball::IsTriplePop()
{
  isChecked= true;
  std::list<Ball*> spheres;
  spheres.push_back(this);

  for (BYTE i= 0; i < 4; ++i)
  {
    auto p= GetAttachPointData(direction(i));
    if (p != nullptr) // Is there anything at the attach point?
      if (p->GetType() == mType) // Is it the same type?
        if (!p->isChecked) // Have we already checked this sphere?
        {
          // This will cause recursion, but it doesn't really matter,
          // because the call stack usually won't be greater than 3 calls.
          // The cast should be absolutely safe.
          std::list<Ball*> spheres2= static_cast<Ball*>(p)->IsTriplePop();
          for_each(spheres2.begin(), spheres2.end(), [&spheres](Ball* pSphere)
          {spheres.push_back(pSphere);});
        }
  }

  return spheres;
}